package web

import (
	"context"
	"net/http"
	"regexp"
	"strings"
	"sync"
)

var (
	pathVariablePattern, _ = regexp.Compile(`{:(\w+)}`)
)

type routeEntry struct {
	pattern      *regexp.Regexp
	pathKeyIndex []string
	h            http.Handler
}

type Route struct {
	entries []routeEntry
	em      map[string]routeEntry
	mu      sync.RWMutex
}

func NewRoute() *Route {
	return &Route{
		entries: make([]routeEntry, 0),
		em:      make(map[string]routeEntry),
		mu:      sync.RWMutex{},
	}
}

func (route *Route) handler(path string) (h http.Handler, pv map[string]string) {
	route.mu.RLock()
	defer route.mu.RUnlock()

	h, pv = route.match(path)
	if h == nil {
		h = http.NotFoundHandler()
	}
	return
}

func (route *Route) match(path string) (http.Handler, map[string]string) {
	path = strings.TrimPrefix(path, "/")
	for _, e := range route.entries {
		matches := e.pattern.FindAllStringSubmatch(path, 1)
		if len(matches) > 0 {
			pathVariables := make(map[string]string)
			for pi, pk := range e.pathKeyIndex {
				pathVariables[pk] = matches[0][pi+1]
			}
			if route, ok := e.h.(*Route); ok {
				h, pv := route.match(path[len(matches[0][0]):])
				if h == nil {
					continue
				}
				for k, v := range pv {
					pathVariables[k] = v
				}

				return h, pathVariables
			}
			if len(path[len(matches[0][0]):]) == 0 {
				return e.h, pathVariables
			}
		}
	}
	return nil, nil
}

func (route *Route) Handle(pattern string, handler http.Handler) {
	route.mu.Lock()
	defer route.mu.Unlock()

	if pattern == "" {
		panic("http: invalid pattern")
	}
	if handler == nil {
		panic("http: nil handler")
	}

	regx, pathKeyIndex, matchPattern := buildMatcher(pattern)
	if _, exist := route.em[matchPattern]; exist {
		panic("http: multiple registrations for " + pattern)
	}

	entry := routeEntry{
		pattern:      regx,
		pathKeyIndex: pathKeyIndex,
		h:            handler,
	}
	route.entries = append(route.entries, entry)
	route.em[matchPattern] = entry
}

func (route *Route) MethodRoute(pattern string) *MethodRoute {
	m := &MethodRoute{}
	route.Handle(pattern, m)
	return m
}

func (route *Route) RestMethodRoute(pattern string) *RestMethodRoute {
	m := &RestMethodRoute{mr: &MethodRoute{}}
	route.Handle(pattern, m)
	return m
}

func (route *Route) SubRoute(pattern string) *Route {
	m := NewRoute()
	route.Handle(pattern, m)
	return m
}

func buildMatcher(pattern string) (regx *regexp.Regexp, keyIndex []string, matchPattern string) {
	ps := strings.Split(strings.Trim(pattern, "/"), "/")
	rs := make([]string, 0)
	for _, p := range ps {
		rp, pv := buildSingleLevelPattern(p)
		if pv != "" {
			keyIndex = append(keyIndex, pv)
		}
		rs = append(rs, rp)
	}
	matchPattern = "^" + strings.Join(rs, "/") + "/?"
	regx, _ = regexp.Compile(matchPattern)
	return
}

func buildSingleLevelPattern(pattern string) (regxPattern string, pathVariable string) {
	matches := pathVariablePattern.FindStringSubmatch(pattern)
	if len(matches) > 0 {
		return "(\\w+)", matches[1]
	}
	return pattern, ""
}

func (route *Route) HandleFunc(pattern string, handler func(w http.ResponseWriter, r *http.Request)) {
	if handler == nil {
		panic("http: nil handler")
	}
	route.Handle(pattern, http.HandlerFunc(handler))
}

func (route *Route) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	h, pv := route.handler(r.URL.Path)
	h.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), "pv", pv)))
}
